package cn.cool.infrastructure.adapter.repository;

import cn.cool.domain.trade.adapter.repository.ITradeRepository;
import cn.cool.domain.trade.model.aggregate.GroupBuyOrderAggregate;
import cn.cool.domain.trade.model.aggregate.GroupBuyTeamSettlementAggregate;
import cn.cool.domain.trade.model.entity.*;
import cn.cool.domain.trade.model.valobj.GroupBuyProgressVO;
import cn.cool.domain.trade.model.valobj.TradeOrderStatusEnumVO;
import cn.cool.infrastructure.dao.IGroupBuyActivityDao;
import cn.cool.infrastructure.dao.IGroupBuyOrderDao;
import cn.cool.infrastructure.dao.IGroupBuyOrderListDao;
import cn.cool.infrastructure.dao.INotifyTaskDao;
import cn.cool.infrastructure.dao.po.GroupBuyActivity;
import cn.cool.infrastructure.dao.po.GroupBuyOrder;
import cn.cool.infrastructure.dao.po.GroupBuyOrderList;
import cn.cool.infrastructure.dao.po.NotifyTask;
import cn.cool.infrastructure.dcc.DCCService;
import cn.cool.infrastructure.limiter.GroupBuyLockOrderRateLimiter;
import cn.cool.infrastructure.redis.IRedisService;
import cn.cool.types.common.Constants;
import cn.cool.types.enums.ActivityStatusEnumVO;
import cn.cool.types.enums.GroupBuyOrderEnumVO;
import cn.cool.types.enums.ResponseCode;
import cn.cool.types.exception.AppException;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import org.apache.commons.lang3.RandomStringUtils;
import org.redisson.api.RMap;
import org.redisson.api.RScript;
import org.redisson.api.RedissonClient;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class TradeRepository implements ITradeRepository {
    @Resource
    private IGroupBuyOrderDao groupBuyOrderDao;

    @Resource
    private IGroupBuyOrderListDao groupBuyOrderListDao;

    @Resource
    private IGroupBuyActivityDao groupBuyActivityDao;

    @Resource
    private INotifyTaskDao notifyTaskDao;

    @Resource
    private DCCService dccService;

    @Resource
    private GroupBuyLockOrderRateLimiter groupBuyLockOrderRateLimiter;

    @Resource
    private IRedisService redisService;

    @Resource
    private RedissonClient redissonClient;

    // Lua脚本：如果key不存在则初始化，存在则不操作
    private static final String INIT_TEAM_DATA_SCRIPT =
            "if redis.call('exists', KEYS[1]) == 0 then " +
            "   redis.call('hset', KEYS[1], 'lock_count', ARGV[1]) " +
            "   redis.call('hset', KEYS[1], 'target_count', ARGV[2]) " +
            "   return 1 " +  // 返回1表示新初始化
            "else " +
            "   return 0 " +  // 返回0表示已存在
            "end";

    // Lua脚本：原子性增加lock_count
    private static final String INCREMENT_LOCK_COUNT_SCRIPT =
            "local lock_count = redis.call('hget', KEYS[1], 'lock_count') " +
            "local target_count = redis.call('hget', KEYS[1], 'target_count') " +
            " " +
            "if lock_count == false or target_count == false then " +
            "   return -1 " +  // 字段不存在
            "end " +
            " " +
            "lock_count = tonumber(lock_count) " +
            "target_count = tonumber(target_count) " +
            " " +
            "if lock_count == nil or target_count == nil then " +
            "   return -2 " +  // 转换失败
            "end " +
            " " +
            "if lock_count < target_count then " +
            "   redis.call('hincrby', KEYS[1], 'lock_count', 1) " +
            "   return 1 " +   // 成功增加
            "else " +
            "   return 0 " +   // 已达到限制
            "end";

    // Lua脚本：直接判断lock_count是否大于等于 target_count
    private static final String CHECK_TEAM_COMPLETE_SCRIPT =
            "local lock_count = tonumber(redis.call('hget', KEYS[1], 'lock_count') or 0) " +
            "local target_count = tonumber(redis.call('hget', KEYS[1], 'target_count') or 0) " +
            " " +
            "if lock_count >= target_count and target_count > 0 then " +
            "   return 1 " +   // 拼团已完成
            "else " +
            "   return 0 " +   // 拼团未完成
            "end";

    @Override
    public MarketPayOrderEntity queryNoPayMarketPayOrderByOutTradeNo(String userId, String outTradeNo) {
        GroupBuyOrderList groupBuyOrderListReq = GroupBuyOrderList.builder().userId(userId).outTradeNo(outTradeNo).build();
        GroupBuyOrderList groupBuyOrderListRes = groupBuyOrderListDao.queryGroupBuyOrderRecordByOutTradeNo(groupBuyOrderListReq);

        if (groupBuyOrderListRes == null) return null;

        return MarketPayOrderEntity.builder()
                .teamId(groupBuyOrderListRes.getTeamId())
                .orderId(groupBuyOrderListRes.getOrderId())
                .deductionPrice(groupBuyOrderListRes.getDeductionPrice())
                .tradeOrderStatusEnumVO(TradeOrderStatusEnumVO.valueOf(groupBuyOrderListRes.getStatus()))
                .build();

    }

    @Override
    public GroupBuyProgressVO queryGroupBuyProgress(String teamId) {
        GroupBuyOrder groupBuyOrder = groupBuyOrderDao.queryGroupBuyProgress(teamId);

        if (groupBuyOrder == null) return null;

        return GroupBuyProgressVO.builder()
                .targetCount(groupBuyOrder.getTargetCount())
                .completeCount(groupBuyOrder.getCompleteCount())
                .lockCount(groupBuyOrder.getLockCount())
                .build();
    }

    @Override
    @Transactional(timeout = 500)
    public MarketPayOrderEntity lockMarketPayOrder(GroupBuyOrderAggregate groupBuyOrderAggregate) {
        // 聚合对象信息
        UserEntity userEntity = groupBuyOrderAggregate.getUserEntity();
        PayActivityEntity payActivityEntity = groupBuyOrderAggregate.getPayActivityEntity();
        PayDiscountEntity payDiscountEntity = groupBuyOrderAggregate.getPayDiscountEntity();
        Integer userTakeOrderCount = groupBuyOrderAggregate.getUserTakeOrderCount();

        // 判断是否有团 - teamId为空 - 新团，不为空-老团
        String teamId = payActivityEntity.getTeamId();
        Long activityId = payActivityEntity.getActivityId();
        if (StrUtil.isBlank(teamId)) {
            // 使用雪花算法获取 uuid
            teamId = RandomStringUtils.randomNumeric(8);

            // redis lua 添加
            boolean init = initTeamDataIfAbsent(String.valueOf(activityId), teamId, payActivityEntity.getTargetCount());

            // 构建拼团订单
            Date currentDate = new Date();
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(currentDate);
            calendar.add(Calendar.MINUTE, payActivityEntity.getValidTime());

            GroupBuyOrder groupBuyOrder = GroupBuyOrder.builder()
                    .teamId(teamId)
                    .activityId(payActivityEntity.getActivityId())
                    .source(payDiscountEntity.getSource())
                    .channel(payDiscountEntity.getChannel())
                    .originalPrice(payDiscountEntity.getOriginalPrice())
                    .deductionPrice(payDiscountEntity.getDeductionPrice())
                    .payPrice(payDiscountEntity.getPayPrice())
                    .targetCount(payActivityEntity.getTargetCount())
                    .completeCount(0)
                    .lockCount(1)
                    .validStartTime(currentDate)
                    .validEndTime(calendar.getTime())
                    .notifyUrl(payDiscountEntity.getNotifyUrl())
                    .build();

            // 写入记录
            groupBuyOrderDao.insert(groupBuyOrder);
        } else {
            // redis lua脚本拦截
            int isLockSuccess = tryIncrementLockCount(String.valueOf(activityId), teamId);
            if (isLockSuccess == 0) {
                throw new AppException(ResponseCode.E0005);
            }
            // 更新记录 - 如果更新记录不等于1，则表示拼团已满，直接抛出异常
            int updateAddLockCount = groupBuyOrderDao.updateAddLockCount(teamId);
            if (updateAddLockCount != 1) {
                throw new AppException(ResponseCode.E0005);
            }
        }

        // 使用 RandomStringUtils.randomNumeric 替代公司里使用的雪花算法UUID
        String orderId = RandomStringUtils.randomNumeric(12);
        GroupBuyOrderList groupBuyOrderListReq = GroupBuyOrderList.builder()
                .userId(userEntity.getUserId())
                .teamId(teamId)
                .orderId(orderId)
                .activityId(payActivityEntity.getActivityId())
                .startTime(payActivityEntity.getStartTime())
                .endTime(payActivityEntity.getEndTime())
                .goodsId(payDiscountEntity.getGoodsId())
                .source(payDiscountEntity.getSource())
                .channel(payDiscountEntity.getChannel())
                .originalPrice(payDiscountEntity.getOriginalPrice())
                .deductionPrice(payDiscountEntity.getDeductionPrice())
                .status(TradeOrderStatusEnumVO.CREATE.getCode())
                .outTradeNo(payDiscountEntity.getOutTradeNo())
                .bizId(payActivityEntity.getActivityId() + Constants.UNDERLINE + userEntity.getUserId() + Constants.UNDERLINE + (userTakeOrderCount + 1))
                .build();

        try {
            // 写入拼团记录
            groupBuyOrderListDao.insert(groupBuyOrderListReq);
        } catch (DuplicateKeyException e) {
            throw new AppException(ResponseCode.INDEX_EXCEPTION);
        }

        return MarketPayOrderEntity.builder()
                .teamId(teamId)
                .orderId(orderId)
                .deductionPrice(payDiscountEntity.getDeductionPrice())
                .tradeOrderStatusEnumVO(TradeOrderStatusEnumVO.CREATE)
                .build();
    }

    /**
     * 初始化团队数据（如果不存在）
     */
    public boolean initTeamDataIfAbsent(String activityId, String teamId, Integer targetCount) {
        String redisKey = "activity-" + activityId + ":teamId-" + teamId;

        try {
            Long result = redissonClient.getScript().eval(
                    RScript.Mode.READ_WRITE,
                    INIT_TEAM_DATA_SCRIPT,
                    RScript.ReturnType.INTEGER,
                    Collections.singletonList(redisKey),
                    1,          // lock_count 初始值
                    targetCount // target_count
            );

            return result != null && result == 1;
        } catch (Exception e) {
            throw new AppException("初始化数据异常");
        }
    }

    /**
     * 尝试增加 lock_count
     * @return 1-成功增加，0-失败（已达到目标数量或数据不存在）
     */
    /**
     * 尝试增加lock_count
     */
    public int tryIncrementLockCount(String activityId, String teamId) {
        String redisKey = "activity-" + activityId + ":teamId-" + teamId;
        try {
            Long result = redissonClient.getScript().eval(
                    RScript.Mode.READ_WRITE,
                    INCREMENT_LOCK_COUNT_SCRIPT,
                    RScript.ReturnType.INTEGER,
                    Collections.singletonList(redisKey)
            );
            return result != null ? result.intValue() : 0;
        } catch (Exception ignore) {
            throw  new AppException("lock_count自增异常");
        }
    }


    @Override
    public GroupBuyActivityEntity queryGroupBuyActivityByActivityId(Long activityId) {
        GroupBuyActivity groupBuyActivity = groupBuyActivityDao.queryGroupBuyActivityByActivityId(activityId);

        return GroupBuyActivityEntity.builder()
                .activityId(groupBuyActivity.getActivityId())
                .activityName(groupBuyActivity.getActivityName())
                .discountId(groupBuyActivity.getDiscountId())
                .groupType(groupBuyActivity.getGroupType())
                .takeLimitCount(groupBuyActivity.getTakeLimitCount())
                .target(groupBuyActivity.getTarget())
                .validTime(groupBuyActivity.getValidTime())
                .status(ActivityStatusEnumVO.valueOf(groupBuyActivity.getStatus()))
                .startTime(groupBuyActivity.getStartTime())
                .endTime(groupBuyActivity.getEndTime())
                .tagId(groupBuyActivity.getTagId())
                .tagScope(groupBuyActivity.getTagScope())
                .build();
    }

    @Override
    public Integer queryOrderCountByActivityId(Long activityId, String userId) {
        GroupBuyOrderList groupBuyOrderList = new GroupBuyOrderList();
        groupBuyOrderList.setActivityId(activityId);
        groupBuyOrderList.setUserId(userId);

        return groupBuyOrderListDao.queryOrderCountByActivityId(activityId, userId);
    }

    @Override
    public GroupBuyTeamEntity queryGroupBuyTeamByTeamId(String teamId) {
        GroupBuyOrder groupBuyOrder = groupBuyOrderDao.queryGroupBuyTeamByTeamId(teamId);

        return GroupBuyTeamEntity.builder()
                .teamId(groupBuyOrder.getTeamId())
                .activityId(groupBuyOrder.getActivityId())
                .targetCount(groupBuyOrder.getTargetCount())
                .completeCount(groupBuyOrder.getCompleteCount())
                .lockCount(groupBuyOrder.getLockCount())
                .status(GroupBuyOrderEnumVO.valueOf(groupBuyOrder.getStatus()))
                .validStartTime(groupBuyOrder.getValidStartTime())
                .validEndTime(groupBuyOrder.getValidEndTime())
                .notifyUrl(groupBuyOrder.getNotifyUrl())
                .build();
    }

    @Transactional(timeout = 500)
    @Override
    public void settlementMarketPayOrder(GroupBuyTeamSettlementAggregate groupBuyTeamSettlementAggregate) {
        UserEntity userEntity = groupBuyTeamSettlementAggregate.getUserEntity();
        GroupBuyTeamEntity groupBuyTeamEntity = groupBuyTeamSettlementAggregate.getGroupBuyTeamEntity();
        TradePaySuccessEntity tradePaySuccessEntity = groupBuyTeamSettlementAggregate.getTradePaySuccessEntity();

        GroupBuyOrderList groupBuyOrderListReq = new GroupBuyOrderList();
        groupBuyOrderListReq.setUserId(userEntity.getUserId());
        groupBuyOrderListReq.setOutTradeNo(tradePaySuccessEntity.getOutTradeNo());
        groupBuyOrderListReq.setOutTradeTime(tradePaySuccessEntity.getOutTradeTime());
        // 更新 group_buy_order_list的 status为已支付
        int updateOrderListStatusCount = groupBuyOrderListDao.updateOrderStatus2Complete(groupBuyOrderListReq);
        if (updateOrderListStatusCount != 1) {
            throw new AppException(ResponseCode.UPDATE_ZERO);
        }

        int updateAddCount = groupBuyOrderDao.updateAddCompleteCount(groupBuyTeamEntity.getTeamId());
        if (updateAddCount != 1) {
            throw new AppException(ResponseCode.UPDATE_ZERO);
        }

        if (groupBuyTeamEntity.getTargetCount() - groupBuyTeamEntity.getCompleteCount() == 1) {
            int updateOrderStatusCount = groupBuyOrderDao.updateOrderStatus2Complete(groupBuyTeamEntity.getTeamId());
            if (updateOrderStatusCount != 1) {
                throw new AppException(ResponseCode.UPDATE_ZERO);
            }

            List<String> outTradeNoList = groupBuyOrderListDao.queryGroupBuyCompleteOrderTradeNoListByTeamId(groupBuyTeamEntity.getTeamId());

            NotifyTask notifyTask = new NotifyTask();
            notifyTask.setActivityId(groupBuyTeamEntity.getActivityId());
            notifyTask.setTeamId(groupBuyTeamEntity.getTeamId());
            notifyTask.setNotifyUrl(groupBuyTeamEntity.getNotifyUrl());
            notifyTask.setNotifyCount(0);
            notifyTask.setNotifyStatus(0);
            notifyTask.setParameterJson(JSONUtil.toJsonStr(
                    new HashMap<String, Object>() {{
                        put("teamId", groupBuyTeamEntity.getTeamId());
                        put("outTradeNoList", outTradeNoList);
                    }}
            ));

            notifyTaskDao.insert(notifyTask);
        }
    }

    @Override
    public boolean isSCBlackIntercept(String userId, String channel) {
        return dccService.isSCBlackIntercept(userId, channel);
    }

    @Override
    public List<NotifyTaskEntity> queryUnExecutedNotifyTaskList() {
        List<NotifyTask> notifyTasks = notifyTaskDao.queryUnExecutedNotifyTaskList();
        return notifyTasks.stream().map(task -> NotifyTaskEntity.builder()
                .teamId(task.getTeamId())
                .notifyUrl(task.getNotifyUrl())
                .notifyCount(task.getNotifyCount())
                .parameterJson(task.getParameterJson())
                .build()).collect(Collectors.toList());
    }

    @Override
    public List<NotifyTaskEntity> queryUnExecutedNotifyTaskList(String teamId) {
        List<NotifyTask> notifyTasks = notifyTaskDao.queryUnExecutedNotifyTaskByTeamId(teamId);
        return notifyTasks.stream().map(task -> NotifyTaskEntity.builder()
                .teamId(task.getTeamId())
                .notifyUrl(task.getNotifyUrl())
                .notifyCount(task.getNotifyCount())
                .parameterJson(task.getParameterJson())
                .build()).collect(Collectors.toList());
    }

    @Override
    public int updateNotifyTaskStateSuccess(String teamId) {
        return notifyTaskDao.updateNotifyTaskStateSuccess(teamId);
    }

    @Override
    public int updateNotifyTaskStateError(String teamId) {
        return notifyTaskDao.updateNotifyTaskStateError(teamId);
    }

    @Override
    public int updateNotifyTaskStateRetry(String teamId) {
        return notifyTaskDao.updateNotifyTaskStateRetry(teamId);
    }

    @Override
    public void lockMarketPayOrderLimit(String activityId, String goodsId) {
        groupBuyLockOrderRateLimiter.checkLimit(activityId, goodsId);
    }

    @Override
    public boolean lockMarketPayOrderDone(String activityId, String teamId) {
        String redisKey = "activity-" + activityId + ":teamId-" + teamId;

        try {
            Long result = redissonClient.getScript().eval(
                    RScript.Mode.READ_ONLY,
                    CHECK_TEAM_COMPLETE_SCRIPT,
                    RScript.ReturnType.INTEGER,
                    Collections.singletonList(redisKey)
            );
            return result != null && result == 1;
        } catch (Exception e) {
            // 异常时降级到数据库查询
            GroupBuyOrder groupBuyOrder = groupBuyOrderDao.queryGroupBuyProgress(teamId);
            return groupBuyOrder != null && groupBuyOrder.getLockCount() >= groupBuyOrder.getTargetCount();
        }
    }
}
